有趣的图形:用Python绘制带饼图的散点图兼论marker的隐藏功能
作者:博观厚积
简书专栏:
https://www.jianshu.com/u/2f376f777ef1
01 带饼图的散点图
有这样一个例子:假设有五个人,每个人的月均收入水平为a=[1,3,2,4,3],消费水平b=[2,1,3,3,5],数据单位均有千元。同时五个人消费水平中,按照每月衣食住行的消费比例为:
s1=[0.1,0.2,0.3,0.4]
s2=[0.35,0.35,0.2,0.1]
s3=[0.2,0.25,0.25,0.3]
s4=[0.5,0.1,0.15,0.25]
s5=[0.0,0.25,0.4,0.35]
依据上述数据(数据纯属虚构),我可以将这些数据画在一个图形里,如图:
(其中,蓝色:衣;黄色:食;红色:住;绿色:行)
这个图形就是带饼图的散点图,从图中不仅可以看出五个人的消费与收入的关系趋势,也可以看出每个人的衣食住行消费比例不同。
是不是很有趣的一个图形?它是怎么做出来的?
02 matplotlib中marker参数的一个隐藏功能
上图是用python中matplotlib包绘制的,而绘制成带饼图的散点图则是用了里边关键的marker参数,所以在介绍如何绘制此图之前,先说说marker参数的一个隐藏功能。
一般的我们绘制散点图基本的命令为:
import matplotlib.pyplot as plt plt.scatter(x, y, s=20, c=None, marker='o')
其中,s是点的大小,c是颜色,marker就是指定点标记的形状,在这里用的就是小圆点o;我们还可以用“*”、“x”、“Δ”等等,甚至还有数字、字母所代表的形状。
事实上,不仅仅如此,在marker的help文档中还指出了一个我们不经常用的标记方式,那就是元组——(numsides, style, angle),numsides是边的个数,angle是旋转角度,style只有0,1,2,3四个值,举个例子。我设置marker=(9,0, 30),就出来个九边形的散点图,如下:
这种定义方式大大拓展了散点形状的自定义方式,本文所做的饼图也源于此。
03 单个带饼图的散点图绘制过程
但是,完全绘制成上述那个图形也并非那么容易,下面我们从一个带饼图的散点绘制讲起。
比如上面的a=1,b=2那个点的消费比例为:s1=[0.1,0.2,0.3,0.4],代码如下:
x = [0] + np.cos(np.linspace(0, 2 * np.pi * 0.1, 5)).tolist()#[0]表示x的初始值 y = [0] + np.sin(np.linspace(0, 2 * np.pi * 0.1, 5)).tolist() #用tolist()形成数列 xy1 = list(zip(x, y)) x = [0] + np.cos(np.linspace(2 * np.pi * 0.1, 2 * np.pi * 0.3, 5)).tolist() y = [0] + np.sin(np.linspace(2 * np.pi * 0.1, 2 * np.pi * 0.3, 5)).tolist() xy2 = list(zip(x, y)) x = [0] + np.cos(np.linspace(2 * np.pi * 0.3, 2 * np.pi* 0.6, 5)).tolist() y = [0] + np.sin(np.linspace(2 * np.pi * 0.3, 2 * np.pi* 0.6, 5)).tolist() xy3 = list(zip(x, y)) x = [0] + np.cos(np.linspace(2 * np.pi * 0.6, 2 * np.pi*1, 5)).tolist() y = [0] + np.sin(np.linspace(2 * np.pi * 0.6, 2 * np.pi*1, 5)).tolist() xy4 = list(zip(x, y)) fig, ax = plt.subplots() ax.scatter(a[0], b[0], marker=(xy1), s=500,facecolor='blue') ax.scatter(a[0], b[0], marker=(xy2), s=500,facecolor='y') ax.scatter(a[0], b[0], marker=(xy3), s=500,facecolor='red') ax.scatter(a[0], b[0], marker=(xy4), s=500,facecolor='green') #为了饼图看得清,散点的size要大一些 plt.show()
得到结果就是:
首先讲讲x、y变量的生成,其原理是先根据每个占比数值所形成的角度(乘以2π,如2 * np.pi * 0.1),然后再用np.linspace函数五等分形成6个角度值,每个值赋予cos、sin函数,这是因为cos2θ+sin2θ=1,所有经过cos、sin函数的值会自动形成一个圆形。
值得注意的是,因为四个占比要围成一个圆形,所以除了第一个占比外,后边的都要用累计占比,如第二个0.2的占比np.linspace(2 * np.pi * 0.1, 2 * np.pi * 0.3, 5),第三个0.3的就是0.3-0.6之间,第四个是0.6-1之间。
如果还没明白那就单独把一个x、y生成的变量,单独作图可以看一下:
x = [0] + np.cos(np.linspace(0, 2 * np.pi * 0.1, 5)).tolist() y = [0] + np.sin(np.linspace(0, 2 * np.pi * 0.1, 5)).tolist() plt.plot(x,y,c='b') plt.show()
可以看出,第一个占比x、y的轨迹就是一个弧形,然后再用初始值(0,0)牵引着,这样再对这个轨迹进行填充时,就会形成一个扇形,如下:
plt.scatter(a[0], b[0], marker=(xy1), s=500,facecolor='blue') plt.show()
这样一个x、y所形成的marker=(xy1)标记,再用facecolor='blue'填充就会形成一个扇形散点标记。最后用fig, ax = plt.subplots()把xy2、xy3、xy4所有子图都放上去,就形成一个圆形,然后用不同颜色填充,就形成一个饼图,每个饼图的角度大小由其占比比例决定。
04 所有点的饼图-散点图
上边是一个点的饼图-散点图,若是将a=[1,3,2,4,3],b=[2,1,3,3,5],以及b的所有个体衣食住行的消费比例全放进去,那就需要用到while、for循环条件,代码如下:
#5个消费水平下衣食住行的占比 s1=[0.1,0.2,0.3,0.4] s2=[0.35,0.35,0.2,0.1] s3=[0.2,0.25,0.25,0.3] s4=[0.5,0.1,0.15,0.25] s5=[0.0,0.25,0.4,0.35] #计算累计占比 ss1=[s1[0],sum(s1[0:2]),sum(s1[0:3]),sum(s1[0:4])] ss2=[s2[0],sum(s2[0:2]),sum(s2[0:3]),sum(s2[0:4])] ss3=[s3[0],sum(s3[0:2]),sum(s3[0:3]),sum(s3[0:4])] ss4=[s4[0],sum(s4[0:2]),sum(s4[0:3]),sum(s4[0:4])] ss5=[s5[0],sum(s5[0:2]),sum(s5[0:3]),sum(s5[0:4])] s=[ss1,ss2,ss3,ss4,ss5] a=[1,3,2,4,3] #收入水平(千元) b=[2,1,3,3,5] #消费水平(千元) fig, ax = plt.subplots(figsize=(10,6)) i=0 while i<len(b): x = [0] + np.cos(np.linspace(0, 2 * np.pi * s[i][0], 15)).tolist() y = [0] + np.sin(np.linspace(0, 2 * np.pi * s[i][0], 15)).tolist() xy1 = list(zip(x, y)) x = [0] + np.cos(np.linspace(2 * np.pi * s[i][0], 2 * np.pi * s[i][1], 15)).tolist() y = [0] + np.sin(np.linspace(2 * np.pi * s[i][0], 2 * np.pi * s[i][1], 15)).tolist() xy2 = list(zip(x, y)) x = [0] + np.cos(np.linspace(2 * np.pi * s[i][1], 2 * np.pi* s[i][2], 15)).tolist() y = [0] + np.sin(np.linspace(2 * np.pi * s[i][1], 2 * np.pi* s[i][2], 15)).tolist() xy3 = list(zip(x, y)) x = [0] + np.cos(np.linspace(2 * np.pi * s[i][2], 2 * np.pi*1, 15)).tolist() y = [0] + np.sin(np.linspace(2 * np.pi * s[i][2], 2 * np.pi*1, 15)).tolist() xy4 = list(zip(x, y)) xy=[xy1,xy2,xy3,xy4] c=['b','y','r','g'] for j in range(4): ax.scatter(a[i], b[i], marker=(xy[j]), s=800,facecolor=c[j]) i=i+1 plt.show()
最终得到本文前边那个图形。
另外还需说明一下,这个图形只适用于小样本数据,也就是图形三点的个数不能太多,每个点中比例数量也不能太多,否则影响展示效果。
写作不易,特别是技术类的写作,请大家多多支持,关注、点赞、转发等等。
赞赏作者
Python爱好者社区历史文章大合集:
Python爱好者社区历史文章列表(每周append更新一次)
关注后在公众号内回复“课程”即可获取:
小编的Python入门视频课程!!!
崔老师爬虫实战案例免费学习视频。
丘老师数据科学入门指导免费学习视频。
陈老师数据分析报告制作免费学习视频。
玩转大数据分析!Spark2.X+Python 精华实战课程免费学习视频。
丘老师Python网络爬虫实战免费学习视频。